#include <NDL.h>
#include <sdl-video.h>
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>

/**
 * Use this function to perform a fast surface copy to a destination surface.
 * 1. The width and height in srcrect determine the size of the copied rectangle. 
 *    Only the position is used in the dstrect (the width and height are ignored). 
 *    Blits with negative dstrect coordinates will be clipped properly.
 * 
 * 2. If srcrect is NULL, the entire surface is copied. 
 *    If dstrect is NULL, then the destination position (upper left corner) is (0, 0).
*/
void SDL_BlitSurface(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect) {
    assert(dst && src);
    assert(dst->format->BitsPerPixel == src->format->BitsPerPixel);

    int w, h;
    int src_x, src_y;
    int dst_x, dst_y;

    if (srcrect == NULL) {
        w = src->w;         h = src->h;
        src_x = 0;          src_y = 0;
    } else {
        w = srcrect->w;     h = srcrect->h;
        src_x = srcrect->x; src_y = srcrect->y;
    }

    if (dstrect == NULL)   { dst_x = 0; dst_y = 0; }
    else { dst_x = dstrect->x; dst_y = dstrect->y; }

    uint8_t* src_p = (uint8_t*)src->pixels;
    uint8_t* dst_p = (uint8_t*)dst->pixels;
    
    int step = 0;

    if      (dst->format->BitsPerPixel == 8)  step = 1;
    else if (dst->format->BitsPerPixel == 32) step = 4;
    else    assert(0);

    uint32_t src_off = (src_y * src->w + src_x) * step;
    uint32_t dst_off = (dst_y * dst->w + dst_x) * step;

    for (int i = 0; i < h; ++i) {
        memcpy(dst_p+dst_off, src_p+src_off, step * w);
        src_off += src->w * step;
        dst_off += dst->w * step;
    }
}

/**
 * This function performs a fast fill of the given rectangle with color. 
 * If dstrect is NULL, the whole surface will be filled with color.
*/
void SDL_FillRect(SDL_Surface *dst, SDL_Rect *dstrect, uint32_t color) {
    assert(dst != NULL);

    int64_t x, y, w, h;
    if (dstrect == NULL) {
        x = 0;      y = 0;
        w = dst->w; h = dst->h;
    } else {
        x = dstrect->x; y = dstrect->y;
        w = dstrect->w; h = dstrect->h;
    }
    // dstrect 可能为 NULL, 不能直接取值
    assert(x + w <= dst->w);
    assert(y + h <= dst->h);

    uint32_t* p = (uint32_t*)dst->pixels + y * dst->w + x;
    uint32_t off = 0;
    for (int i = 0; i < h; ++i) {
        for (int j = 0; j < w; ++j) {
            p[off+j] = color;
        }
        off += dst->w;
    }
}

/**
 * void SDL_UpdateRect(SDL_Surface *screen, Sint32 x, Sint32 y, Sint32 w, Sint32 h);
 * Makes sure the given area is updated on the given "screen". 
 * The rectangle must be confined within the "screen" boundaries (no clipping is done).
 * If 'x', 'y', 'w' and 'h' are all 0, SDL_UpdateRect will update the entire "screen".
 * https://www.php.cn/mysql-tutorials-122484.html
 * https://en.wikibooks.org/wiki/SDL_(Simple_DirectMedia_Layer)
 * 本人理解: 将 s 在 (x,y) 处 w*h 的 Rect 更新到屏幕上 
*/
#define BUF_LEN 1000*1000*4
static uint8_t buf[BUF_LEN];
void SDL_UpdateRect(SDL_Surface *s, int x, int y, int w, int h) {
    if (w == 0) w = s->w;
    if (h == 0) h = s->h;
    
    assert(s != NULL);
    assert(x + w <= s->w);
    assert(y + h <= s->h);

    if (s->format->BitsPerPixel == 32) {
        uint32_t* dst = (uint32_t*)buf;
        uint32_t* src = (uint32_t*)s->pixels + y * s->w + x;
        for (int i = 0; i < h; ++i) {
            memcpy(dst + i * w, src + i * s->w, w * 4);
        }
        NDL_DrawRect(dst, x, y, w, h);
        return;	
    }

    if (s->format->BitsPerPixel == 8) {
        uint32_t* dst = (uint32_t*)buf;
        SDL_Color* colors = s->format->palette->colors;
        uint32_t dst_off = 0;
        uint32_t src_off = y * s->w + x;
        for (int i = 0; i < h; ++i) {
            for (int j = 0; j < w; ++j) {
                SDL_Color color = colors[s->pixels[src_off + j]];
                dst[dst_off + j] = (color.a << 24) | (color.r << 16) | (color.g << 8) | (color.b );
            }
            dst_off += w;
            src_off += s->w;
        }
        NDL_DrawRect(dst, x, y, w, h);
        return;
    }
    
    assert(0);
}

// APIs below are already implemented.

static inline int maskToShift(uint32_t mask) {
    switch (mask) {
        case 0x000000ff: return 0;
        case 0x0000ff00: return 8;
        case 0x00ff0000: return 16;
        case 0xff000000: return 24;
        case 0x00000000: return 24; // hack
        default: assert(0);
    }
}

SDL_Surface* SDL_CreateRGBSurface(uint32_t flags, int width, int height, int depth,
        uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) {
    assert(depth == 8 || depth == 32);
    SDL_Surface *s = malloc(sizeof(SDL_Surface));
    assert(s);
    s->flags = flags;
    s->format = malloc(sizeof(SDL_PixelFormat));
    assert(s->format);
    if (depth == 8) {
        s->format->palette = malloc(sizeof(SDL_Palette));
        assert(s->format->palette);
        s->format->palette->colors = malloc(sizeof(SDL_Color) * 256);
        assert(s->format->palette->colors);
        memset(s->format->palette->colors, 0, sizeof(SDL_Color) * 256);
        s->format->palette->ncolors = 256;
    } else {
        s->format->palette = NULL;
        s->format->Rmask = Rmask; s->format->Rshift = maskToShift(Rmask); s->format->Rloss = 0;
        s->format->Gmask = Gmask; s->format->Gshift = maskToShift(Gmask); s->format->Gloss = 0;
        s->format->Bmask = Bmask; s->format->Bshift = maskToShift(Bmask); s->format->Bloss = 0;
        s->format->Amask = Amask; s->format->Ashift = maskToShift(Amask); s->format->Aloss = 0;
    }

    s->format->BitsPerPixel = depth;
    s->format->BytesPerPixel = depth / 8;

    s->w = width;
    s->h = height;
    s->pitch = width * depth / 8;
    assert(s->pitch == width * s->format->BytesPerPixel);

    if (!(flags & SDL_PREALLOC)) {
        s->pixels = malloc(s->pitch * height);
        assert(s->pixels);
    }

    return s;
}

SDL_Surface* SDL_CreateRGBSurfaceFrom(void *pixels, int width, int height, int depth,
        int pitch, uint32_t Rmask, uint32_t Gmask, uint32_t Bmask, uint32_t Amask) {
    SDL_Surface *s = SDL_CreateRGBSurface(SDL_PREALLOC, width, height, depth,
            Rmask, Gmask, Bmask, Amask);
    assert(pitch == s->pitch);
    s->pixels = pixels;
    return s;
}

void SDL_FreeSurface(SDL_Surface *s) {
    if (s != NULL) {
        if (s->format != NULL) {
            if (s->format->palette != NULL) {
                if (s->format->palette->colors != NULL) free(s->format->palette->colors);
                free(s->format->palette);
            }
            free(s->format);
        }
        if (s->pixels != NULL && !(s->flags & SDL_PREALLOC)) free(s->pixels);
        free(s);
    }
}


/**
 * 设置显示模式, 
 * Set up a video mode with the specified width, height and bits-per-pixel.
 * The flags parameter is the same as the flags field of the SDL_Surface structure.
 * flags: SDL_HWSURFACE	Create the video surface in video memory
*/
SDL_Surface* SDL_SetVideoMode(int width, int height, int bpp, uint32_t flags) {
    if (flags & SDL_HWSURFACE) NDL_OpenCanvas(&width, &height);
    return SDL_CreateRGBSurface(flags, width, height, bpp,
            DEFAULT_RMASK, DEFAULT_GMASK, DEFAULT_BMASK, DEFAULT_AMASK);
}

void SDL_SoftStretch(SDL_Surface *src, SDL_Rect *srcrect, SDL_Surface *dst, SDL_Rect *dstrect) {
    assert(src && dst);
    assert(dst->format->BitsPerPixel == src->format->BitsPerPixel);
    assert(dst->format->BitsPerPixel == 8);

    int x = (srcrect == NULL ? 0 : srcrect->x);
    int y = (srcrect == NULL ? 0 : srcrect->y);
    int w = (srcrect == NULL ? src->w : srcrect->w);
    int h = (srcrect == NULL ? src->h : srcrect->h);

    assert(dstrect);
    if(w == dstrect->w && h == dstrect->h) {
        /* The source rectangle and the destination rectangle
         * are of the same size. If that is the case, there
         * is no need to stretch, just copy. */
        SDL_Rect rect;
        rect.x = x;
        rect.y = y;
        rect.w = w;
        rect.h = h;
        SDL_BlitSurface(src, &rect, dst, dstrect);
    }
    else {
        assert(0);
    }
}

void SDL_SetPalette(SDL_Surface *s, int flags, SDL_Color *colors, int firstcolor, int ncolors) {
    assert(s);
    assert(s->format);
    assert(s->format->palette);
    assert(firstcolor == 0);

    s->format->palette->ncolors = ncolors;
    memcpy(s->format->palette->colors, colors, sizeof(SDL_Color) * ncolors);

    if(s->flags & SDL_HWSURFACE) {
        assert(ncolors == 256);
        for (int i = 0; i < ncolors; i ++) {
            uint8_t r = colors[i].r;
            uint8_t g = colors[i].g;
            uint8_t b = colors[i].b;
        }
        SDL_UpdateRect(s, 0, 0, 0, 0);
    }
}

static void ConvertPixelsARGB_ABGR(void *dst, void *src, int len) {
    int i;
    uint8_t (*pdst)[4] = dst;
    uint8_t (*psrc)[4] = src;
    union {
        uint8_t val8[4];
        uint32_t val32;
    } tmp;
    int first = len & ~0xf;
    for (i = 0; i < first; i += 16) {
#define macro(i) \
        tmp.val32 = *((uint32_t *)psrc[i]); \
        *((uint32_t *)pdst[i]) = tmp.val32; \
        pdst[i][0] = tmp.val8[2]; \
        pdst[i][2] = tmp.val8[0];

        macro(i + 0); macro(i + 1); macro(i + 2); macro(i + 3);
        macro(i + 4); macro(i + 5); macro(i + 6); macro(i + 7);
        macro(i + 8); macro(i + 9); macro(i +10); macro(i +11);
        macro(i +12); macro(i +13); macro(i +14); macro(i +15);
    }

    for (; i < len; i ++) {
        macro(i);
    }
}

SDL_Surface *SDL_ConvertSurface(SDL_Surface *src, SDL_PixelFormat *fmt, uint32_t flags) {
    assert(src->format->BitsPerPixel == 32);
    assert(src->w * src->format->BytesPerPixel == src->pitch);
    assert(src->format->BitsPerPixel == fmt->BitsPerPixel);

    SDL_Surface* ret = SDL_CreateRGBSurface(flags, src->w, src->h, fmt->BitsPerPixel,
        fmt->Rmask, fmt->Gmask, fmt->Bmask, fmt->Amask);

    assert(fmt->Gmask == src->format->Gmask);
    assert(fmt->Amask == 0 || src->format->Amask == 0 || (fmt->Amask == src->format->Amask));
    ConvertPixelsARGB_ABGR(ret->pixels, src->pixels, src->w * src->h);

    return ret;
}

uint32_t SDL_MapRGBA(SDL_PixelFormat *fmt, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
    assert(fmt->BytesPerPixel == 4);
    uint32_t p = (r << fmt->Rshift) | (g << fmt->Gshift) | (b << fmt->Bshift);
    if (fmt->Amask) p |= (a << fmt->Ashift);
    return p;
}

int SDL_LockSurface(SDL_Surface *s) {
    assert(0);
    return 0;
}

void SDL_UnlockSurface(SDL_Surface *s) {
}
